package org.finesys.quartz.support;

import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.finesys.common.core.util.DateUtils;
import org.finesys.common.quartz.core.constants.SchedulerConstants;
import org.finesys.quartz.entity.SysJob;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.util.*;

@Component
@RequiredArgsConstructor
public class JobScheduler {

    private final Scheduler scheduler;

    /**
     * 组件jobKey
     */
    private JobKey getJobKey(SysJob sysJob) {
        return JobKey.jobKey(SchedulerConstants.TASK_CLASS_NAME + sysJob.getJobId(), sysJob.getJobGroup());
    }

    /**
     * 生成任务触发对象key
     */
    private TriggerKey getTriggerKey(SysJob sysJob) {
        return TriggerKey.triggerKey(SchedulerConstants.TASK_CLASS_NAME + sysJob.getJobId(), sysJob.getJobGroup());
    }

    /**
     * 判断执行策略
     */
    private CronScheduleBuilder makeCronScheduleBuilder(SysJob sysJob) {
        String misfirePolicy = sysJob.getMisfirePolicy();
        String cronExpress = sysJob.getCronExpression();
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpress);
        switch (misfirePolicy) {
            case SchedulerConstants.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW:
                cronScheduleBuilder.withMisfireHandlingInstructionFireAndProceed();
                return cronScheduleBuilder;
            case SchedulerConstants.MISFIRE_INSTRUCTION_DO_NOTHING:
                cronScheduleBuilder.withMisfireHandlingInstructionDoNothing();
                return cronScheduleBuilder;
            case SchedulerConstants.MISFIRE_INSTRUCTION_IGNORE:
                cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires();
                return cronScheduleBuilder;
            case SchedulerConstants.MISFIRE_DEFAULT:
            default:
                return cronScheduleBuilder;
        }
    }


    /**
     * 新增定时任务
     */
    @SneakyThrows
    public boolean add(SysJob sysJob) {
        //获取job唯一标识
        JobKey jobKey = getJobKey(sysJob);
        //构建jobDetail
        JobDetail jobDetail;
        if (SchedulerConstants.QUARTZ_SERIAL.equals(sysJob.getJobRunType())) {
            jobDetail = JobBuilder.newJob(QuartzJobSerialBean.class).withIdentity(jobKey).build();
        } else {
            jobDetail = JobBuilder.newJob(QuartzJobParallelBean.class).withIdentity(jobKey).build();
        }
        //放置job明细信息
        jobDetail.getJobDataMap().put(SchedulerConstants.SCHEDULE_JOB_KEY, sysJob);
        //时间
        Date startTime = DateUtils.localDateTimeToDate(sysJob.getStartTime());
        Date endTime = DateUtils.localDateTimeToDate(sysJob.getEndTime());
        /**定时处理*/
        if (StringUtils.hasText(sysJob.getCronExpression())) {
            CronScheduleBuilder cronScheduleBuilder = makeCronScheduleBuilder(sysJob);
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(sysJob))
                    .startAt(startTime)
                    .endAt(endTime)
                    .withSchedule(cronScheduleBuilder)
                    .withPriority(sysJob.getJobPriority()).build();
            scheduler.scheduleJob(jobDetail, trigger);
        } else {
            CalendarIntervalScheduleBuilder calendarIntervalScheduleBuilder = makeCalendarIntervalScheduleBuilder(sysJob);
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(sysJob)).startAt(startTime).endAt(endTime).withSchedule(calendarIntervalScheduleBuilder).withPriority(sysJob.getJobPriority()).build();
            scheduler.scheduleJob(jobDetail, trigger);
        }
        // 构建执行表达式
        return true;
    }

    /**
     * 修改定时任务
     */
    public boolean update(SysJob sysJob) {
        //先删除
        delete(sysJob);
        //在新增
        add(sysJob);
        return true;
    }

    @SneakyThrows
    public boolean delete(SysJob sysJob) {
        //检查任务名称
        Assert.notNull(sysJob.getJobName(), String.format("定时任务名称未设置"));
        JobKey jobKey = getJobKey(sysJob);
        TriggerKey triggerKey = getTriggerKey(sysJob);
        //暂停触发器
        scheduler.pauseTrigger(triggerKey);
        //移除触发器
        scheduler.unscheduleJob(triggerKey);
        //删除任务
        scheduler.deleteJob(jobKey);
        return true;
    }

    @SneakyThrows
    public boolean pause(SysJob sysJob) {
        //检查任务名称
        Assert.notNull(sysJob.getJobName(), String.format("定时任务名称未设置"));
        JobKey jobKey = getJobKey(sysJob);
        //暂停任务
        scheduler.pauseJob(jobKey);
        return true;
    }

    @SneakyThrows
    public boolean resume(SysJob sysJob) {
        //检查任务名称
        Assert.notNull(sysJob.getJobName(), String.format("定时任务名称未设置"));
        JobKey jobKey = getJobKey(sysJob);
        TriggerKey triggerKey = getTriggerKey(sysJob);
        //恢复触发器
        scheduler.resumeTrigger(triggerKey);
        //恢复任务
        scheduler.resumeJob(jobKey);

        return true;
    }

    /**
     * 启动全部暂停的任务
     */
    @SneakyThrows
    public boolean startAll() {
        scheduler.resumeAll();
        return true;
    }

    /**
     * 暂停全部的任务
     */
    @SneakyThrows
    public boolean stopAll() {
        scheduler.pauseAll();
        return true;
    }

    /**
     * 执行定时任务
     */
    @SneakyThrows
    public boolean executeJob(SysJob sysJob) {
        //检查任务名称
        Assert.notNull(sysJob.getJobName(), String.format("定时任务名称未设置"));
        // 参数
        JobDataMap dataMap = new JobDataMap();
        dataMap.put(SchedulerConstants.SCHEDULE_JOB_KEY, sysJob);
        JobKey jobKey = getJobKey(sysJob);
        scheduler.triggerJob(jobKey, dataMap);
        return true;
    }

    @SneakyThrows
    public List<SysJob> findList(SysJob sysJob) {
        List<SysJob> sysJobList = new ArrayList<>();
        GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
        String keyWord = sysJob.getJobName();
        Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
        if (ObjectUtils.isEmpty(jobKeys)) {
            return sysJobList;
        }
        for (JobKey jobKey : jobKeys) {
            //不为空，且不包含查询条件，则跳过
            if (StringUtils.hasText(keyWord) && !jobKey.getName().contains(keyWord)) {
                continue;
            }

            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            SysJob result = new SysJob();
            result.setJobName(jobKey.getName().replace(SchedulerConstants.TASK_CLASS_NAME, ""));
            result.setJobGroup(jobKey.getGroup());
            result.setClassName(jobDetail.getJobClass().getName());
            setJob(jobKey, jobDetail, result);
            sysJobList.add(sysJob);
        }
        return sysJobList;
    }

    @SneakyThrows
    public SysJob selectByName(SysJob sysJob) {
        //检查任务名称
        Assert.notNull(sysJob.getJobName(), String.format("定时任务名称未设置"));
        JobKey jobKey = getJobKey(sysJob);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        SysJob result = new SysJob();
        result.setJobName(sysJob.getJobName());
        result.setJobGroup(sysJob.getJobGroup());
        result.setClassName(jobDetail.getJobClass().getName());
        setJob(jobKey, jobDetail, result);
        return result;
    }

    @SneakyThrows
    public boolean exsitsJob(SysJob sysJob) {
        //检查任务名称
        Assert.notNull(sysJob.getJobName(), String.format("定时任务名称未设置"));
        JobKey jobKey = getJobKey(sysJob);
        TriggerKey triggerKey = getTriggerKey(sysJob);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        Trigger trigger = scheduler.getTrigger(triggerKey);
        if (Objects.nonNull(jobDetail) && Objects.nonNull(trigger)) {
            return true;
        }
        return false;
    }

    /**
     * 设置sysJob
     */
    private List<? extends Trigger> setJob(JobKey jobKey, JobDetail jobDetail, SysJob sysJob) throws SchedulerException {
        //获取触发器?
        List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
        Trigger trigger = triggers.get(0);
        sysJob.setStartTime(DateUtils.dateToLocalDateTime(trigger.getStartTime()));
        sysJob.setEndTime(DateUtils.dateToLocalDateTime(trigger.getEndTime()));
        if (trigger instanceof CronTrigger) {
            CronTriggerImpl cronTrigger = (CronTriggerImpl) trigger;
            sysJob.setCronExpression(cronTrigger.getCronExpression());
        }
        if (trigger instanceof CalendarIntervalTrigger) {
            CalendarIntervalTrigger calendarIntervalTrigger = (CalendarIntervalTrigger) trigger;
            sysJob.setIntervalUnit(calendarIntervalTrigger.getRepeatIntervalUnit().name());
            sysJob.setTimeInterval(calendarIntervalTrigger.getRepeatInterval());
        }
        sysJob.setJobPriority(trigger.getPriority());
        sysJob.setJobExecuteStatus(scheduler.getTriggerState(trigger.getKey()).name());
        return triggers;
    }

    /**
     * 判断执行策略
     */
    private CalendarIntervalScheduleBuilder makeCalendarIntervalScheduleBuilder(SysJob sysJob) {
        DateBuilder.IntervalUnit cycleUnit = DateBuilder.IntervalUnit.valueOf(sysJob.getIntervalUnit());
        Integer timeInterval = sysJob.getTimeInterval();
        CalendarIntervalScheduleBuilder scheduleBuilder = CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withInterval(timeInterval, cycleUnit);
        String misfirePolicy = sysJob.getMisfirePolicy();
        switch (misfirePolicy) {
            case SchedulerConstants.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW:
                scheduleBuilder.withMisfireHandlingInstructionFireAndProceed();
                return scheduleBuilder;
            case SchedulerConstants.MISFIRE_INSTRUCTION_DO_NOTHING:
                scheduleBuilder.withMisfireHandlingInstructionDoNothing();
                return scheduleBuilder;
            case SchedulerConstants.MISFIRE_INSTRUCTION_IGNORE:
                scheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires();
                return scheduleBuilder;
            case SchedulerConstants.MISFIRE_DEFAULT:
            default:
                return scheduleBuilder;
        }
    }
}
